package edu.thu.thss.yxy.sip.sdp;

import java.io.UnsupportedEncodingException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.URL;
import java.util.List;
import java.util.Vector;

import javax.sdp.Connection;
import javax.sdp.Media;
import javax.sdp.MediaDescription;
import javax.sdp.Origin;
import javax.sdp.SdpException;
import javax.sdp.SdpFactory;
import javax.sdp.SdpParseException;
import javax.sdp.SessionDescription;
import javax.sdp.TimeDescription;
import javax.sdp.Version;
import javax.sip.header.ContentTypeHeader;

import edu.thu.thss.yxy.util.Logger;

/**
 * The class contains a number of utility methods that are meant to facilitate
 * creating and parsing SDP descriptions.
 * 
 * @author Andy Gao
 *
 */
@SuppressWarnings("unchecked")
public class SdpUtils {
	 /**
     * Our class logger.
     */
    private static final Logger logger = Logger.getLogger(SdpUtils.class);

    /**
     * A reference to the currently valid SDP factory instance.
     */
    private static final SdpFactory sdpFactory = SdpFactory.getInstance();

    /**
     * Parses the specified <tt>sdp String</tt> into a
     * <tt>SessionDescription</tt> and returns it;
     *
     * @param sdp the <tt>sdp String</tt> that we'd like to parse.
     *
     * @return the <tt>SessionDescription</tt> instance corresponding to the
     * specified <tt>sdp String</tt>.
     *
     * @throws IllegalArgumentException in case <tt>sdp</tt> is not a valid
     * SDP <tt>String</tt>.
     */
    public static SessionDescription parseSdpString(String sdp)
        throws IllegalArgumentException
    {
        try
        {
            return sdpFactory.createSessionDescription(sdp);
        }
        catch (SdpParseException ex)
        {
            throw new IllegalArgumentException(
                "Failed to parse the SDP description of the peer.", ex);
        }
    }

    /**
     * Creates an empty instance of a <tt>SessionDescription</tt> with
     * preinitialized  <tt>s</tt>, <tt>v</tt>, <tt>c</tt>, <tt>o</tt> and
     * <tt>t</tt> parameters.
     *
     * @param localAddress the <tt>InetAddress</tt> corresponding to the local
     * address that we'd like to use when talking to the remote party.
     *
     * @return an empty instance of a <tt>SessionDescription</tt> with
     * preinitialized <tt>s</tt>, <tt>v</tt>, and <tt>t</tt> parameters.
     */
    public static SessionDescription createSessionDescription(
                    InetAddress localAddress)
    {
        return createSessionDescription(localAddress, null, null);
    }

    /**
     * Creates an empty instance of a <tt>SessionDescription</tt> with
     * preinitialized  <tt>s</tt>, <tt>v</tt>, <tt>c</tt>, <tt>o</tt> and
     * <tt>t</tt> parameters.
     *
     * @param localAddress the <tt>InetAddress</tt> corresponding to the local
     * address that we'd like to use when talking to the remote party.
     * @param userName the user name to use in the origin parameter or
     * <tt>null</tt> in case we'd like to use a default.
     * @param mediaDescriptions a <tt>Vector</tt> containing the list of
     * <tt>MediaDescription</tt>s that we'd like to advertise (leave
     * <tt>null</tt> if you'd like to add these later).
     *
     * @return an empty instance of a <tt>SessionDescription</tt> with
     * preinitialized <tt>s</tt>, <tt>v</tt>, and <tt>t</tt> parameters.
     */
    public static SessionDescription createSessionDescription(
                                   InetAddress              localAddress,
                                   String                   userName,
                                   Vector<MediaDescription> mediaDescriptions)
    {
        SessionDescription sessDescr = null;

        try
        {
            sessDescr = sdpFactory.createSessionDescription();

            //"v=0"
            Version v = sdpFactory.createVersion(0);

            sessDescr.setVersion(v);

            //"s=-"
            sessDescr.setSessionName(sdpFactory.createSessionName("-"));

            //"t=0 0"
            TimeDescription t = sdpFactory.createTimeDescription();
            Vector<TimeDescription> timeDescs = new Vector<TimeDescription>();
            timeDescs.add(t);

            sessDescr.setTimeDescriptions(timeDescs);

            String addrType = localAddress instanceof Inet6Address
                ? Connection.IP6
                : Connection.IP4;

            //o
            if (userName == null)
                userName = "sip-communicator";

            Origin o = sdpFactory.createOrigin(
                userName,
                0,
                0,
                "IN",
                addrType,
                localAddress.getHostAddress());

            sessDescr.setOrigin(o);

            //c=
            Connection c = sdpFactory.createConnection(
                "IN", addrType, localAddress.getHostAddress());

            sessDescr.setConnection(c);

            if ( mediaDescriptions != null)
                sessDescr.setMediaDescriptions(mediaDescriptions);

            return sessDescr;
        }
        catch (SdpException exc)
        {
            //the jain-sip implementations of the above methods do not throw
            //exceptions in the cases we are using them so falling here is
            //quite unlikely. we are logging out of mere decency :)
            logger.error("Failed to crate an SDP SessionDescription.", exc);
        }

        return sessDescr;
    }

    

    /**
	 * Extracts all the audio codecs from the description of the media session of the incoming request
	 * @param sessionDescription - the description of the media session of the incoming request
	 * @return List of all the audio codecs from the description of the media session of the incoming request
	 */
	public static List extractAudioCodecs(SessionDescription sessionDescription) {
		List audioCodecList = new Vector();
		Vector<MediaDescription> mediaDescriptionList = null;
		try {
			mediaDescriptionList = sessionDescription.getMediaDescriptions(true);
		} catch (SdpException se) {
			logger.debug("A funny thing just happened ...", se);
		}
		try {
			for (int i = 0; i < mediaDescriptionList.size(); i++) {
				MediaDescription mediaDescription = mediaDescriptionList.elementAt(i);
				Media media = mediaDescription.getMedia();
				if (media.getMediaType().equals("audio"))
					audioCodecList = media.getMediaFormats(true);
			}
		} catch (SdpParseException spe) {
			logger.debug("A funny thing just happened ...", spe);
		}
		return audioCodecList;
	}
	
	/**
	 * Extracts all the video codecs from the description of the media session of the incoming request
	 * @param sessionDescription - the description of the media session of the incoming request
	 * @return List of all the audio codecs from the description of the media session of the incoming request
	 */
	public static List extractVideoCodecs(SessionDescription sessionDescription) {
		List videoCodecList = new Vector();
		Vector mediaDescriptionList = null;
		try {
			mediaDescriptionList =
				sessionDescription.getMediaDescriptions(true);
		} catch (SdpException se) {
			se.printStackTrace();
		}
		try {
			for (int i = 0; i < mediaDescriptionList.size(); i++) {
				MediaDescription mediaDescription =
					(MediaDescription) mediaDescriptionList.elementAt(i);
				Media media = mediaDescription.getMedia();
				if (mediaDescription.getMedia().getMediaType().equals("video"))
					videoCodecList = media.getMediaFormats(true);
			}
		} catch (SdpParseException spe) {
			spe.printStackTrace();
		}
		return videoCodecList;
	}

	/**
	 * Extracts the audio port from the description of the media session of the incoming request
	 * @param sessionDescription - the description of the media session of the incoming request
	 * @return the audio port on which is listening the remote user agent
	 */
	public static int getAudioPort(SessionDescription sessionDescription) {
		Vector mediaDescriptionList = null;
		try {
			mediaDescriptionList =
				sessionDescription.getMediaDescriptions(true);
		} catch (SdpException se) {
			se.printStackTrace();
		}
		try {
			for (int i = 0; i < mediaDescriptionList.size(); i++) {
				MediaDescription mediaDescription =
					(MediaDescription) mediaDescriptionList.elementAt(i);
				if (mediaDescription.getMedia().getMediaType().equals("audio"))
					return mediaDescription.getMedia().getMediaPort();
			}
		} catch (SdpParseException spe) {
			spe.printStackTrace();
		}
		return -1;
	}

	/**
	 * Extracts the video port from the description of the media session of the incoming request
	 * @param sessionDescription - the description of the media session of the incoming request
	 * @return the video port on which is listening the remote user agent
	 */
	
	public static int getVideoPort(SessionDescription sessionDescription) {
		Vector mediaDescriptionList = null;
		try {
			mediaDescriptionList =
				sessionDescription.getMediaDescriptions(true);
		} catch (SdpException se) {
			se.printStackTrace();
		}
		try {
			for (int i = 0; i < mediaDescriptionList.size(); i++) {
				MediaDescription mediaDescription =
					(MediaDescription) mediaDescriptionList.elementAt(i);
				if (mediaDescription.getMedia().getMediaType().equals("video"))
					return mediaDescription.getMedia().getMediaPort();
			}
		} catch (SdpParseException spe) {
			spe.printStackTrace();
		}
		return -1;
	}

    /**
     * Returns a <tt>URL</tt> pointing to a location with more details (and
     * possibly call control utilities) about the session. This corresponds to
     * the <tt>"u="</tt> field of the SDP data.
     *
     * @param sessDesc the session description that we'd like to extract an
     * <tt>URL</tt> form.
     *
     * @return a <tt>URL</tt> pointing to a location with more details about
     * the session or <tt>null</tt> if the remote party did not provide one.
     */
    public static URL getCallInfoURL(SessionDescription sessDesc)
    {
        javax.sdp.URI sdpUriField = sessDesc.getURI();

        if (sdpUriField == null)
        {
            logger.trace("Call URI was null.");
            return null;
        }

        try
        {
            return sdpUriField.get();
        }
        catch (SdpParseException exc)
        {
            logger.warn("Failed to parse SDP URI.", exc);
            return null;
        }

    }


    /**
     * Extracts and returns all <tt>MediaDescription</tt>s provided in
     * <tt>sessionDescription</tt>.
     *
     * @param sessionDescription the <tt>SessionDescription</tt> that we'd like
     * to extract <tt>MediaDescription</tt>s from.
     *
     * @return a non <tt>null</tt> <tt>Vector</tt> containing all media
     * descriptions from the <tt>sessionDescription</tt>.
     *
     * @throws IllegalArgumentException in case there were no media descriptions
     * in <tt>sessionDescription</tt>.
     */
    public static Vector<MediaDescription> extractMediaDescriptions(
                    SessionDescription sessionDescription)
        throws IllegalArgumentException
    {
        Vector<MediaDescription> remoteDescriptions = null;
        try
        {
            remoteDescriptions = sessionDescription.getMediaDescriptions(false);
        }
        catch (SdpException e)
        {
            // ignoring as remoteDescriptions would remain null and we will
            // log and rethrow right underneath.
        }

        if(remoteDescriptions == null || remoteDescriptions.size() == 0)
        {
            throw new IllegalArgumentException(
                "Could not find any media descriptions.");
        }

        return remoteDescriptions;
    }

    /**
     * Gets the content of the specified SIP <tt>Message</tt> in the form of a
     * <tt>String</tt> value.
     *
     * @param message the SIP <tt>Message</tt> to get the content of
     * @return a <tt>String</tt> value which represents the content of the
     * specified SIP <tt>Message</tt>
     */
    public static String getContentAsString(javax.sip.message.Message message)
    {
        byte[] rawContent = message.getRawContent();

        /*
         * If rawContent isn't in the default charset, its charset is in the
         * Content-Type header.
         */
        ContentTypeHeader contentTypeHeader
            = (ContentTypeHeader) message.getHeader(ContentTypeHeader.NAME);
        String charset = null;

        if (contentTypeHeader != null)
            charset = contentTypeHeader.getParameter("charset");
        if (charset == null)
            charset = "UTF-8"; // RFC 3261

        try
        {
            return new String(rawContent, charset);
        }
        catch (UnsupportedEncodingException uee)
        {
            logger.warn("SIP message with unsupported charset of its content",
                    uee);
            /*
             * We failed to do it the right way so just do what we used to do
             * before.
             */
            return new String(rawContent);
        }
    }
}
